from twisted.internet import reactor, protocol
from twisted.protocols import basic

import carddeck, cardhand, carddealer, cardplayer
import copy
import cPickle
import message
import sched, time
from threading import Thread, Event, enumerate
import signal
# NEVER USE sendLine() outside of the sendOne() function!

class Game:
    handDelay = 2.0
    turnDelay = 2.0
    gameStartDelay = 2.0

    currentPlayer = 0
    howManyPlayers = 0
    gameStarted = False
    changedPlayers = []

    continueLoopingEvent = Event()
    def __init__(self, chatFactory):
        self.continueLoopingEvent.set()

        self.factory = chatFactory
        self.deck = carddeck.Deck(carddeck.DECKSIZE)
        self.deck.shuffle()

        self.dealer = carddealer.Dealer()
        self.dealer.new_hand(self.deck.deal_card(), self.deck.deal_card())

        self.players = [None] * 5
        self.players.append(self.dealer)

    def AddPlayer(self, name):
        self.changedPlayers.append((True, name))

    def RemovePlayer(self, name):
        self.changedPlayers.append((False, name))

    def UpdatePlayers(self):
        # player
        # (True,playerName) == Add
        # (False,playerName) == Delete
        for player in self.changedPlayers:
            if player[0]:
                for i in range(0, 5):
                    if not self.players[i]:
                        self.players[i] = cardplayer.Player(player[1])
                        break
            else:
                for i in range(0, 5):
                    if self.players[i]:
                        if self.players[i].name == player[1]:
                            self.players[i] = None
                            break
        self.changedPlayers = []
        if len(self.players) == 0:
            self.gameStarted = False
            self.continueLoopingEvent.clear()

    def SetCurrentPlayer(self):
        print 'Attempting to SetCurrentPlayer'
        count = 0
        for player in self.players:
            if player:
                if player.myTurn:
                    break
            count += 1
        print "count after who's turn loop: " + str(count)

        if count >= 5:
            count = 0
            for player in self.players:
                    if player:
                        player.myTurn = True
                        self.currentPlayer = count
                        break
                    count += 1
            if count == 5:
                self.players[5].myTurn = False
        else:
            self.players[count].myTurn = False
            for i in range(count + 1, 6):
                if self.players[i]:
                    self.players[i].myTurn = True
                    self.currentPlayer = i
                    break
        print 'Current player = ' + str(self.currentPlayer)
        print 'Exiting SetCurrentPlayer()'

    def StartGame(self):
        print 'Attempting to Start Game'
        if not self.gameStarted:

            self.gameStarted = True
            time.sleep(self.gameStartDelay)
            self.StartHand()
            print "Exiting StartGame()"
            return 'Hand beginning in ' + str(self.handDelay) + ' seconds.'
        else:
            print "Exiting StartGame()"
            return 'Game in progress.'

    def StartHand(self):
        while True:
            self.UpdatePlayers()
            if self.gameStarted:
                time.sleep(self.handDelay)
                if len(self.factory.clients) != 0:
                    self.factory.clients[0].sendMsg('chat', 'Cooldown --- Hand starting in ' + str(self.handDelay) + ' seconds.')
                self.SetCurrentPlayer()

                print 'Attempting to Start Hand'
                for player in self.players:
                    if player:
                        player.new_hand(self.deck.deal_card(), self.deck.deal_card())

                for player in self.players:
                    if player:
                        print player.name + ": " + str(player)
                print str(len(self.factory.clients)) + ' client(s) currently connected.'

                if len(self.factory.clients) != 0:
                    self.factory.clients[0].sendPlayerStates()

                    for player in range(0, 6):
                        if self.players[player]:
                            self.players[player].myTurn = True
                            if len(self.factory.clients) != 0:
                                self.factory.clients[0].sendPlayerStates()
                            self.StartTurn()
                else:
                    self.gameStarted = False
                    self.continueLoopingEvent.clear()
                    self.continueLoopingEvent.wait()
                print 'Exiting StartHand()'
            else:
                break

    def StartTurn(self):
        print 'Attempting to Start Turn'
        self.continueLoopingEvent.clear()
        if self.currentPlayer != 5:
            self.continueLoopingEvent.wait(self.turnDelay)

        print self.players[self.currentPlayer].name + "'s turn"
        print 'Exiting StartTurn()'

        self.EndTurn()

    def EndTurn(self):
        print 'Attempting to End Turn'
#        self.players[self.currentPlayer].myTurn = False
        if self.currentPlayer == 5:
            self.DealersTurn()

            self.WriteToDB()
            for player in self.players:
                if player:
                    player.reset()

        self.SetCurrentPlayer()
        print 'Exiting Game.EndTurn()'
#        if len(self.factory.clients) != 0:
#            self.factory.clients[0].sendPlayerStates()
    def DealersTurn(self):
        if len(self.factory.clients) != 0:
            # This displays the Dealer's down card.
            self.factory.clients[0].sendPlayerStates(True)
        if self.players[5]._get_total() <= 16:
            sentinel = True
            while sentinel:
                sentinel = self.players[5].hit(self.deck.deal_card())
                print 'Hit: Dealer'
            if len(self.factory.clients) != 0:
                self.factory.clients[0].sendPlayerStates(True)
        else:
            print "Dealer's hand totals: " + str(self.players[5]._get_total())
    def Hit(self, name):
        print 'Attempting to Hit'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i < 5:
            if self.players[i].myTurn:
                if self.players[i]._get_total() < 21:
                    self.players[i].hit(self.deck.deal_card())
                    print 'Hit: ' + name
                    return True
        print 'Exiting Game.Hit()'

    def Stand(self, name):
        print 'Attempting to Stand'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i != 6:
                if self.players[i].myTurn:
                    self.continueLoopingEvent.set()
                    print name + ' stands.'
        else:
            print name + "'s stand failed."
        print 'Exiting Game.Stand()'

    def Bet(self, name, amount):
        print 'Attempting to change bet'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i < 5:
            self.players[i].current_bet = amount
        else:
            print name + "'s stand failed."
        print 'Exiting Game.Bet()'

    def WriteToDB(self):
        pass

def t():
    return "[" + time.strftime("%H:%M:%S") + "] "

class EchoProtocol(basic.LineReceiver):
    def __init__(self):
        self.name = "Unnamed"
        self.lineBuffer = None

    # Once a local list of Players is implemented use this to update the client(s) current state.
    # def updateNames(self):
        # sendMsg(players)

    def connectionMade(self):
        self.sendOne("chat", "Welcome, what is your name?")
        # self.sendOne("chat", "")
        self.count = 0
        self.factory.clients.append(self)
        print t() + "+ Connection from: " + self.transport.getPeer().host

    def connectionLost(self, reason):
        self.sendMsg("chat", "- %s left." % self.name)
        print t() + "- Connection lost: " + self.name
        self.factory.clients.remove(self)
        self.factory.game.RemovePlayer(self.name)


    def lineReceived(self, line):
        lines = line.rstrip("\t\r\n").split("\t")
        # lines.pop() # Last item in the list will always be an empty string since all lines are \t terminated
        if self.lineBuffer:
            lines[0] = self.lineBuffer + lines[0]
            self.lineBuffer = None
        for stripMe in lines:
                item = stripMe.strip("\r\n")
                try:
                    undata = cPickle.loads(item)
                    if isinstance(undata, message.Message):
                        print type(undata)
                        if undata.type == "chat":
                            if undata.payload == 'quit':
                                self.sendOne("chat", "Goodbye.")
                                self.transport.loseConnection()
                                return
                            elif undata.payload == "userlist":
                                self.chatters()
                                return
                            self.sendMsg("chat", self.name + ": " + undata.payload)
                            return
                        elif undata.type == "auth":
                            self.username(undata.payload)
                        elif undata.type == "hit":
                            if self.factory.game.Hit(self.name):
                                self.sendPlayerStates()
                        elif undata.type == "stand":
                            self.factory.game.Stand(self.name)
                        elif undata.type == "bet":
                            self.factory.game.Bet(self.name, self.payload)
                        elif undata.type == "quit":
                            pass
                except (cPickle.UnpicklingError, EOFError) as e:
                    self.lineBuffer = stripMe
                    print type(e)

    def username(self, line):
        for x in self.factory.clients:
            if x.name == line:
                self.sendOne("chat", "This username is taken, please choose another\n")
                return

        self.name = line
        self.sendOne("auth", "okay:" + self.name)
        self.chatters()
        self.sendOne("chat", "Chat away!")
        # self.sendOne("chat", "")
        self.count += 1
        self.sendOne("chat", "+ %s joined.\n" % self.name)

        self.factory.game.AddPlayer(self.name)
        if not self.factory.game.gameStarted:
            if self.factory.gameThread:
                self.factory.game.gameStarted = True
                self.factory.game.continueLoopingEvent.set()
            else:
                self.sendMsg('chat', 'Beginning game.')
                self.factory.gameThread = Thread(target=self.factory.game.StartGame)
                self.factory.gameThread.start()
                print 'Living threads:'
                print repr(enumerate())
        else:
            self.sendMsg('chat', 'Game already in progress.')
#        sendMe = self.factory.game.StartGame()
#        self.sendMsg('chat',sendMe)

        print '%s~ %s is now known as %s\n' % (t(), self.transport.getPeer().host, self.name)

    def chatters(self):
        x = len(self.factory.clients)
        s = 'is' if x == 1 else 'are'
        p = 'person' if x == 1 else 'people'
        self.sendOne("chat", "There %s %i %s connected:" % (s, x, p))

        for client in self.factory.clients:
            # if client is not self:
            self.sendOne("chat", client.name)
        # self.sendOne("chat", "")

    def sendPlayerStates(self, showDealearHand=False):
        if showDealearHand:
            self.sendMsg("userStates", self.factory.game.players)
        else:
            temp = copy.deepcopy(self.factory.game.players)
            temp[5].new_hand(temp[5].current_hand[0], None)
            self.sendMsg("userStates", temp)
            del temp

    # Use this to send a message to all connected clients
    def sendMsg(self, messageType, msg):
        for client in self.factory.clients:
            client.sendLine(cPickle.dumps(message.Message(messageType, msg)) + "\t")

    # Use this to communicate with a single client
    def sendOne(self, messageType, msg):
        self.sendLine(cPickle.dumps(message.Message(messageType, msg)) + "\t")

class EchoServerFactory(protocol.ServerFactory):
        def startFactory(self):
            self.game = Game(self)
            self.protocol = EchoProtocol
            self.clients = []
            self.gameThread = None

        def stopFactory(self):
            print "Shutting down Factory."
            while self.gameThread.is_alive():
#                print 'Trying to force thread to return.'
                self.game.gameStarted = False
                self.game.continueLoopingEvent.set()
            print 'Living threads:'
            print repr(enumerate())
            print "Exiting stopFactory()"


def onClose(signal, frame):
    print 'Caught ctrl-c'
    reactor.stop()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, onClose,)
    reactor.listenTCP(5001, EchoServerFactory())
    reactor.run()
